/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.jfonia.view.symbols;

import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import org.jfonia.connect5.basics.BasicValueNode;
import org.jfonia.connect5.basics.MutableValueNode;
import org.jfonia.connect5.basics.ValueNode;
import org.jfonia.connect5.basics.Observer;
import org.jfonia.connect5.intervals.BasicDoubleInterval;
import org.jfonia.connect5.intervals.LinkedDoubleIntervalList;
import org.jfonia.connect5.numerical.UnidirectionalDoubleSumRelation;
import org.jfonia.connect5.relations.Equal;
import org.jfonia.connect5.relations.UnidirectionalABRelation;
import org.jfonia.connect5.relations.UnidirectionalEqual;
import org.jfonia.constants.SymbolConstants;
import org.jfonia.constants.ViewConstants;
import org.jfonia.view.relations.DoubleInversionRelation;
import org.jfonia.notation.ObservableNote;

/**
 *
 * @author Rik Bauwens
 */
public class NoteSymbol extends ComplexStaffSymbol
{
        private ObservableNote note;//private Tone tone;

        private IStaffSymbol accidentalSymbol;
        private IStaffSymbol noteHeadSymbol;
        private IStaffSymbol stemSymbol;
        private IStaffSymbol flagSymbols[];
        private IStaffSymbol dotSymbols[];
        private IStaffSymbol ledgerLineSymbols[];

        private LinkedDoubleIntervalList noteWidth;
        private BasicDoubleInterval noteHeadWidth;
        private BasicDoubleInterval stemWidth;

        private MutableValueNode<Integer> stemDirectionNode;
        private MutableValueNode<Integer> flagNode;

        public NoteSymbol(ValueNode<Double> frameX, ValueNode<Double> staffY,
                          MutableValueNode<Double> staffBase, MutableValueNode<Double> postStaffBase,
                          ValueNode<Double> xNode, MutableValueNode<Double> widthNode, ObservableNote note /*Tone tone,
                          MutableValueNode<Integer> noteHeadNode, MutableValueNode<Integer> stemNode,
                          MutableValueNode<Integer> flagNode*/)
        {
                super(frameX, staffY, staffBase, postStaffBase, xNode, widthNode, note.getRankNode());
                this.note = note;//this.tone = tone;

                flagSymbols = new IStaffSymbol[0];
                dotSymbols = new IStaffSymbol[0];
                ledgerLineSymbols = new IStaffSymbol[0];
                stemDirectionNode = new BasicValueNode<Integer>(0);
                new IntConditionRelation(getRankNode(), stemDirectionNode, 
                                         SymbolConstants.STEM_DIRECTION_SWITCH,
                                         SymbolConstants.STEM_DIRECTION_UP,
                                         SymbolConstants.STEM_DIRECTION_UP,
                                         SymbolConstants.STEM_DIRECTION_DOWN);
                flagNode = new BasicValueNode<Integer>(0);
                new Equal<Integer>(note.getFlagsNode(), flagNode);
                init(frameX, staffY, staffBase, postStaffBase, xNode, 
                     note.getNoteHeadNode(), note.getStemNode(), note.getFlagsNode());
        }

        private void init(ValueNode<Double> frameX, ValueNode<Double> staffY,
                          MutableValueNode<Double> staffBase, MutableValueNode<Double> postStaffBase,
                          ValueNode<Double> xNode, MutableValueNode<Integer> noteHeadNode,
                          MutableValueNode<Integer> stemNode, MutableValueNode<Integer> flagNode)
        {
                //Rank
                MutableValueNode<Integer> rankToCentralCNode = note.getRankNode();

                //Note width
                noteWidth = new LinkedDoubleIntervalList();
                new UnidirectionalEqual<Double>(xNode, noteWidth.getBeginNode());

                //Accidental
                BasicDoubleInterval accidentalWidth = new BasicDoubleInterval();
                noteWidth.add(accidentalWidth);
                accidentalSymbol = new AccidentalSymbol(frameX, staffY, staffBase, postStaffBase,
                        accidentalWidth.getBeginNode(), accidentalWidth.getDifferenceNode(),
                        rankToCentralCNode, note.getAccidentalNode());

                //Notehead
                noteHeadWidth = new BasicDoubleInterval();
                noteWidth.add(noteHeadWidth);
                noteHeadSymbol = new NoteHeadSymbol(frameX, staffY, staffBase, postStaffBase, 
                        noteHeadWidth.getBeginNode(), noteHeadWidth.getDifferenceNode(),
                        rankToCentralCNode, noteHeadNode);
                noteHeadNode.addObserver(new Observer()
                {
                        public void onNotify(Object source)
                        {
                                MutableValueNode<Integer> noteHeadNode = (MutableValueNode<Integer>) source;
                                stemDirectionNode.setValue(0);
                                if(noteHeadNode.getValue().compareTo(SymbolConstants.NOTEHEAD_SEMIBREVE) < 0)
                                        stemDirectionNode.setValue(note.getStaffRank()
                                            <= SymbolConstants.STEM_DIRECTION_SWITCH ?
                                            SymbolConstants.STEM_DIRECTION_UP :
                                            SymbolConstants.STEM_DIRECTION_DOWN );
                        }
                });

                //Stem
                stemDirectionNode.addObserver(new Observer()
                {
                        public void onNotify(Object source)
                        {
                                //Remove interval from list if it's already added
                                //to add it again in the right place
                                int index = noteWidth.indexOf(stemWidth);
                                if(index >= 0)
                                        noteWidth.remove(index);
                                //Check stem direction
                                int stemDirection = ((MutableValueNode<Integer>) source).getValue();
                                if(stemDirection == SymbolConstants.STEM_DIRECTION_DOWN)
                                {
                                        noteWidth.insert(noteWidth.indexOf(noteHeadWidth), stemWidth);
                                        makeFlags();
                                }
                                else if(stemDirection == SymbolConstants.STEM_DIRECTION_UP)
                                {
                                        noteWidth.insert(noteWidth.indexOf(noteHeadWidth) + 1, stemWidth);
                                        makeFlags();
                                }
                        }
                });
                stemWidth = new BasicDoubleInterval();
                int stemDirection = stemDirectionNode.getValue();
                if(stemDirection == SymbolConstants.STEM_DIRECTION_DOWN)
                        noteWidth.insert(noteWidth.indexOf(noteHeadWidth), stemWidth);
                else if(stemDirection == SymbolConstants.STEM_DIRECTION_UP)
                        noteWidth.insert(noteWidth.indexOf(noteHeadWidth) + 1, stemWidth);
                stemSymbol = new StemSymbol(frameX, staffY, staffBase, postStaffBase,
                                stemWidth.getBeginNode(), stemWidth.getDifferenceNode(),
                                rankToCentralCNode, stemNode, stemDirectionNode);

                //Flags
                flagNode.addObserver(new Observer()
                {
                        public void onNotify(Object source)
                        {
                                makeFlags();
                        }
                });
                makeFlags();

                //Dots
                int dotCount = 0;
                BasicDoubleInterval dotsWidth = new BasicDoubleInterval();
                noteWidth.add(dotsWidth);
                dotSymbols = makeDots(frameX, staffY, staffBase, postStaffBase, 
                        dotsWidth.getBeginNode(), dotsWidth.getDifferenceNode(),
                        rankToCentralCNode, dotCount);

                //Ledger lines
                getRankNode().addObserver(new Observer(){
                        public void onNotify(Object source)
                        {
                                makeLedgerLines();
                        }
                });
                makeLedgerLines();

        }

        public ObservableNote getNote()
        {
                return note;
        }

        private void makeFlags()
        {
                int flagCount = flagNode.getValue();
                int stemDirection = stemDirectionNode.getValue();
                BasicDoubleInterval flagWidth = new BasicDoubleInterval();
                //Check stem direction
                if(stemDirection == SymbolConstants.STEM_DIRECTION_DOWN)
                {
                        new Equal(stemWidth.getEndNode(), flagWidth.getBeginNode());
                }
                else if(stemDirection == SymbolConstants.STEM_DIRECTION_UP)
                {
                        MutableValueNode<Double> invertedNode = new BasicValueNode<Double>(0.0);
                        new DoubleInversionRelation(stemWidth.getDifferenceNode(), invertedNode);
                        new UnidirectionalDoubleSumRelation(stemWidth.getBeginNode(),
                                                            invertedNode, flagWidth.getBeginNode());
                }
                ValueNode<Double> xNode = flagWidth.getBeginNode();
                ValueNode<Double> yNode = ((StemSymbol) stemSymbol).getStemTailNode();

                flagSymbols = new IStaffSymbol[flagCount];
                //Y positions of flags towards the notehead (when flagCount <= 2)
                LinkedDoubleIntervalList negFlagOffsets = new LinkedDoubleIntervalList();
                new UnidirectionalEqual<Double>(yNode, negFlagOffsets.getBeginNode());
                //Y positions of flags away from the notehead (when flagCount > 2)
                LinkedDoubleIntervalList posFlagOffsets = new LinkedDoubleIntervalList();
                new UnidirectionalDoubleSumRelation(yNode, 
                        new BasicValueNode<Double>(SymbolConstants.FLAG_SPACING),
                        posFlagOffsets.getBeginNode());

                for(int i = 0; i < flagCount; i++)
                {
                        BasicDoubleInterval flagOffset = new BasicDoubleInterval();
                        if((i < 2 && stemDirection == SymbolConstants.FLAG_STEM_DOWN)
                        || (i >= 2 && stemDirection == SymbolConstants.FLAG_STEM_UP))
                        {
                                flagOffset.setDifference(-SymbolConstants.FLAG_SPACING);
                                negFlagOffsets.add(flagOffset);
                        }
                        else if((i < 2 && stemDirection == SymbolConstants.FLAG_STEM_UP)
                             || (i >= 2 && stemDirection == SymbolConstants.FLAG_STEM_DOWN))
                        {
                                flagOffset.setDifference(SymbolConstants.FLAG_SPACING);
                                posFlagOffsets.add(flagOffset);
                        }
                        flagSymbols[i] = new FlagSymbol(getFrameX(), getStaffY(), xNode, flagOffset.getBeginNode(),
                                new BasicValueNode<Integer>(stemDirection));
                }
        }

        private IStaffSymbol[] makeDots(ValueNode<Double> frameX, ValueNode<Double> staffY,
                                   MutableValueNode<Double> staffBase, MutableValueNode<Double> postStaffBase,
                                   ValueNode<Double> xNode, MutableValueNode<Double> widthNode,
                                   MutableValueNode<Integer> rankNode, int dotCount)
        {
                IStaffSymbol[] res = new IStaffSymbol[dotCount];
                //X positions of dots
                LinkedDoubleIntervalList dotOffsets = new LinkedDoubleIntervalList();
                new UnidirectionalEqual<Double>(xNode, dotOffsets.getBeginNode());
                new UnidirectionalEqual<Double>(dotOffsets.getDifferenceNode(), widthNode);

                for(int i = 0; i < dotCount; i++)
                {
                        BasicDoubleInterval dotWidth = new BasicDoubleInterval();
                        dotOffsets.add(dotWidth);
                        res[i] = new DotSymbol(frameX, staffY, staffBase, postStaffBase,
                                dotWidth.getBeginNode(), dotWidth.getDifferenceNode(), rankNode);
                }
                return res;
        }

        private void makeLedgerLines()
        {
                int rank = getRank();
                int lineCount = 0;
                if(rank < 0)
                        lineCount = Math.abs(rank / 2);
                else if(rank - ((ViewConstants.STAFF_LINE_COUNT - 1) * 2) > 0)
                        lineCount = (rank - ((ViewConstants.STAFF_LINE_COUNT - 1) * 2)) / 2;
                ledgerLineSymbols = new IStaffSymbol[lineCount];

                //Ledger lines are only drawn on even ranks
                if(rank % 2 != 0)
                {
                        if(rank < 0)
                                rank++;
                        else
                                rank--;
                }

                //Create the ledger lines
                for(int i = 0; i < lineCount; i++)
                {
                        ledgerLineSymbols[i] = new LedgerLineSymbol(getFrameX(), getStaffY(),
                                               getStaffBase(), getPostStaffBase(),
                                               noteHeadWidth.getBeginNode(),
                                               noteHeadWidth.getDifferenceNode(),
                                               new BasicValueNode<Integer>(rank));
                        //Lines under the staff
                        if(rank < 0)
                                rank += 2;
                        //Lines above the staff
                        else
                                rank -= 2;
                }
        }

        private void calculateBounds()
        {
                //Initialize bounds
                rectangle = new Rectangle2D.Double(getX(), getY(), 0, 0);
                //Calculate bounds
                if(accidentalSymbol != null)
                        rectangle.add(accidentalSymbol.getBounds());
                if(noteHeadSymbol != null)
                        rectangle.add(noteHeadSymbol.getBounds());
                if(stemSymbol != null)
                        rectangle.add(stemSymbol.getBounds());
                for(IStaffSymbol flagSymbol : flagSymbols)
                        rectangle.add(flagSymbol.getBounds());
                for(IStaffSymbol dotSymbol : dotSymbols)
                        rectangle.add(dotSymbol.getBounds());
                //Update nodes (apply bounds)
                updateNodes();
        }

        @Override
        public void paintSymbol(Graphics2D g)
        {
                super.paintSymbol(g);
                if(accidentalSymbol != null)
                        accidentalSymbol.paintSymbol(g);
                if(noteHeadSymbol != null)
                        noteHeadSymbol.paintSymbol(g);
                if(stemSymbol != null)
                        stemSymbol.paintSymbol(g);
                for(IStaffSymbol flagSymbol : flagSymbols)
                        flagSymbol.paintSymbol(g);
                for(IStaffSymbol dotSymbol : dotSymbols)
                        dotSymbol.paintSymbol(g);
                for(IStaffSymbol ledgerLineSymbol : ledgerLineSymbols)
                        ledgerLineSymbol.paintSymbol(g);
                calculateBounds();
        }

        //If the value of a is less than the conditionValue, the value of b is set to lessValue;
        //if the value of a is equal to the conditionValue, the value of b is set to equalValue;
        //if the value of a is greater than the conditionValue, the value of b is set to greaterValue
        public class IntConditionRelation extends UnidirectionalABRelation<Integer, Integer>
        {
                private int conditionValue;
                private int lessValue;
                private int equalValue;
                private int greaterValue;

                public IntConditionRelation(ValueNode<Integer> a, MutableValueNode<Integer> b,
                                            int conditionValue, int lessValue,
                                            int equalValue, int greaterValue)
                {
                        super(a, b);
                        this.conditionValue = conditionValue;
                        this.lessValue = lessValue;
                        this.equalValue = equalValue;
                        this.greaterValue = greaterValue;
                        updateB();
                }

                @Override
                public void updateB()
                {
                        int cmp = a.getValue().compareTo(conditionValue);
                        if(cmp < 0)
                                b.setValue(lessValue);
                        else if(cmp == 0)
                                b.setValue(equalValue);
                        else if(cmp > 0)
                                b.setValue(greaterValue);
                }

                @Override
                public void updateA() {}
        }

}
